/*
 * File     : BufferParameter.java
 * Created  : 22 Apr 2011
 *
 * Copyright © 2011 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.dni.internal.call;

import static com.googlecode.dni.internal.DniTypes.LIBRARY_HELPER_TYPE;
import static org.objectweb.asm.Opcodes.*;

import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.ShortBuffer;

import org.objectweb.asm.MethodVisitor;

import com.googlecode.dni.internal.DniInternal;
import com.googlecode.dni.internal.MemoryAccess;
import com.googlecode.dni.library.IllegalLibraryException;


/**
 * <p>
 *  Encapsulates a parameter to a native call that comes from a {@link Buffer}.
 * </p>
 *
 * @author Matthew Wilson
 */
final class BufferParameter extends Parameter
{

    private final int fixedSize;

    private int fixedSizeInBytes = -1;


    /**
     * Creates a new instance that cannot be used as a return parameter.
     */
    BufferParameter()
    {
        this.fixedSize = -1;
        setNativeTypeAsPointer();
    }


    /**
     * Creates a new instance that can be used as a return parameter.
     *
     * @param fixedSize
     *            the fixed size
     */
    BufferParameter( final int fixedSize )
    {
        this.fixedSize = fixedSize;
        setNativeTypeAsPointer();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void setJavaType( final Class< ? > javaType )
    {
        super.setJavaType( javaType );

        if ( this.fixedSize >= 0 )
        {
            int unit;
            if ( javaType == ByteBuffer.class )
            {
                unit = 1;
            }
            else if ( javaType == CharBuffer.class || javaType == ShortBuffer.class )
            {
                unit = 2;
            }
            else if ( javaType == IntBuffer.class || javaType == FloatBuffer.class )
            {
                unit = 4;
            }
            else if ( javaType == LongBuffer.class || javaType == DoubleBuffer.class )
            {
                unit = 8;
            }
            else
            {
                throw new IllegalLibraryException( "Unsupported buffer type: " + javaType.getName() );
            }

            long total = unit * (long) this.fixedSize;
            if ( total >= Integer.MAX_VALUE )
            {
                throw new IllegalLibraryException( "Fixed size buffer too big: "
                                                   + javaType.getSimpleName() + " " + this.fixedSize );
            }
            this.fixedSizeInBytes = (int) total;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    int getJavaVarSlots()
    {
        return 1;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void writeGetParameter( final MethodVisitor methodVisitor )
    {
        if ( DniInternal.DEBUG_MODE && this.fixedSize >= 0 )
        {
            methodVisitor.visitVarInsn( ALOAD, getJavaVarIndex() );
            DniInternal.writeConstantInt( methodVisitor, this.fixedSizeInBytes );
            methodVisitor.visitMethodInsn( INVOKESTATIC,
                                           LIBRARY_HELPER_TYPE,
                                           "checkFixedSizeBuffer",
                                           "(Ljava/nio/Buffer;I)J" );
        }

        methodVisitor.visitVarInsn( ALOAD, getJavaVarIndex() );

        methodVisitor.visitMethodInsn( INVOKESTATIC,
                                       MemoryAccess.UNSAFE_ACCESS_TYPE,
                                       "getBufferPointer",
                                       "(Ljava/nio/Buffer;)J" );

        if ( getNativeType() == 'I' )
        {
            methodVisitor.visitInsn( L2I );
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void writeReturnParameter( final MethodVisitor methodVisitor,
                               final int returnVarIndex )
    {
        if ( this.fixedSize < 0 )
        {
            throw new UnsupportedOperationException();
        }

        if ( getNativeType() == 'I' )
        {
            methodVisitor.visitVarInsn( ILOAD, returnVarIndex );
            methodVisitor.visitInsn( I2L );
            methodVisitor.visitLdcInsn( 0xffffffffL );
            methodVisitor.visitInsn( LAND );
        }
        else
        {
            assert getNativeType() == 'J';

            methodVisitor.visitVarInsn( LLOAD, returnVarIndex );
        }

        DniInternal.writeConstantInt( methodVisitor, this.fixedSizeInBytes );

        methodVisitor.visitMethodInsn( INVOKESTATIC,
                                       LIBRARY_HELPER_TYPE,
                                       "get" + getJavaType().getSimpleName(),
                                       "(JI)L" + getJavaType().getName().replace( '.', '/' ) + ";" );

        methodVisitor.visitInsn( ARETURN );
    }

}
